package com.local.security.config;

import com.local.security.filter.LoginFilter;
import com.local.security.handler.LoginAuthenticationFailureHandler;
import com.local.security.handler.LoginAuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @author lou ke
 * @since 2021/3/5 16:04
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private LoginAuthenticationSuccessHandler loginAuthenticationSuccessHandler;
    @Autowired
    private LoginAuthenticationFailureHandler loginAuthenticationFailureHandler;


    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    /**
     * 用自定义的过滤器来代替 UsernamePasswordAuthenticationFilter
     *
     * @return LoginFilter
     * @throws Exception 异常
     */
    @Bean
    LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setAuthenticationSuccessHandler(loginAuthenticationSuccessHandler);
        loginFilter.setAuthenticationFailureHandler(loginAuthenticationFailureHandler);
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        loginFilter.setFilterProcessesUrl("/doLogin");
        return loginFilter;
    }

    /**
     * 重写WebSecurityConfigurerAdapter 里面的方法
     *
     * @param auth AuthenticationManagerBuilder
     * @throws Exception 异常
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("lk")
                .password("1234")
                .roles("admin");
    }

    @Override
    public void configure(WebSecurity web) {
        // 配置忽略掉的url地址，一般对于静态文件，可以采用该操作
        web.ignoring().antMatchers("/js/**", "/css/**", "/images/**");
    }

    /**
     * @param http HttpSecurity
     * @throws Exception 异常
     *                   <p>
     *                   1、 web.ignoring() 用来配置忽略掉的 URL 地址，一般对于静态文件，我们可以采用此操作。
     *                   2、如果我们使用 XML 来配置 Spring Security ，里边会有一个重要的标签  ，HttpSecurity 提供的配置方法 都对应了该标签。
     *                   3、authorizeRequests 对应了  。
     *                   4、 formLogin 对应了  。
     *                   5、 and 方法表示结束当前标签，上下文回到HttpSecurity，开启新一轮的配置。
     *                   6、 permitAll 表示登录相关的页面/接口不要被拦截。
     *                   7、最后记得关闭 csrf ，关于 csrf 问题我到后面专门和大家说。
     *                   <p>
     *                   关于defaultSuccessUrl 和 successForwardUrl
     *                   1、defaultSuccessUrl 有一个重载的方法，我们先说一个参数的 defaultSuccessUrl 方法。如果我们在 defaultSuccessUrl 中指定
     *                   登录成功的跳转页面为 /index，此时分两种情况， 如果你是直接在浏览器中输入的登录地址，登录成功后，就直接跳转到 /index，如果你是
     *                   在浏览器中输入了其他地址，例如 http://localhost:8080/hello，结果因为没有登录， 又重定向到登录页面，此时登录成功后，就不会
     *                   来到 /index ，而是来到 /hello 页面。
     *                   2、defaultSuccessUrl 还有一个重载的方法，第二个参数如果不设置默认为 false，也就是我们上面的的情况，如果手动设置第二个参数为
     *                   true，则 defaultSuccessUrl 的效果和 successForwardUrl 一致。
     *                   3、successForwardUrl 表示不管你是从哪里来的，登录后一律跳转到 successForwardUrl 指定的地址。例如 successForwardUrl
     *                   指定的地址为 /index ， 你在浏览器地址栏输入http://localhost:8080/hello，结果因为没有登录，重定向到登录页面，当你登录成功
     *                   之后，就会服务端跳转到 /index 页面；或者你直接就在浏览器输入了登录 页面地址，登录成功后也是来到 /index。
     *                   <p>
     *                   关于failureForwardUrl 和 failureUrl
     *                   这两个方法在设置的时候也是设置一个即可。failureForwardUrl 是登录失败之后会发生服务端跳转，failureUrl 则在登录失败之后，会发生重定向。
     *                   <p>
     *                   注销登录
     *                   1、默认注销的 URL 是 /logout，是一个 GET 请求，我们可以通过 logoutUrl 方法来修改默认的注销 URL。
     *                   2、logoutRequestMatcher 方法不仅可以修改注销 URL，还可以修改请求方式，实际项目中，这个方法和 logoutUrl 任意设置一个即可。
     *                   3、logoutSuccessUrl 表示注销成功后要跳转的页面。
     *                   4、deleteCookies 用来清除 cookie。
     *                   5、clearAuthentication 和 invalidateHttpSession 分别表示清除认证信息和使 HttpSession 失效，默认可以不用配置，默认就会清除。
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and() // 登录相关配置
                .formLogin()
                .loginPage("/login.html") // 登录界面 get
                .loginProcessingUrl("/doLogin") // 登录接口 post
                .usernameParameter("name") // 用户名
                .passwordParameter("passwd") // 密码
                .defaultSuccessUrl("/index", true)
                .permitAll()
                .and() // 登出相关配置
                .logout()
                .logoutUrl("/logout")
                // .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "POST"))
                .logoutSuccessUrl("/index")
                .deleteCookies()
                .clearAuthentication(true)
                .invalidateHttpSession(true)
                .and()
                .csrf().disable();
        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}
